/*
 * Copyright 2004-2021 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.ibatis.sqlmap.engine.config;

import com.ibatis.sqlmap.client.extensions.*;
import com.ibatis.sqlmap.engine.impl.*;
import com.ibatis.sqlmap.engine.mapping.result.*;
import com.ibatis.sqlmap.engine.scope.*;
import com.ibatis.sqlmap.engine.type.*;

import java.util.*;

/**
 * The Class ResultMapConfig.
 */
public class ResultMapConfig {

  /** The config. */
  private SqlMapConfiguration config;

  /** The error context. */
  private ErrorContext errorContext;

  /** The client. */
  private SqlMapClientImpl client;

  /** The delegate. */
  private SqlMapExecutorDelegate delegate;

  /** The type handler factory. */
  private TypeHandlerFactory typeHandlerFactory;

  /** The result map. */
  private ResultMap resultMap;

  /** The result mapping list. */
  private List resultMappingList;

  /** The result mapping index. */
  private int resultMappingIndex;

  /** The discriminator. */
  private Discriminator discriminator;

  /**
   * Instantiates a new result map config.
   *
   * @param config
   *          the config
   * @param id
   *          the id
   * @param resultClass
   *          the result class
   * @param groupBy
   *          the group by
   * @param extendsResultMap
   *          the extends result map
   * @param xmlName
   *          the xml name
   */
  ResultMapConfig(SqlMapConfiguration config, String id, Class resultClass, String groupBy, String extendsResultMap,
      String xmlName) {
    this.config = config;
    this.errorContext = config.getErrorContext();
    this.client = config.getClient();
    this.delegate = config.getDelegate();
    this.typeHandlerFactory = config.getTypeHandlerFactory();
    this.resultMap = new ResultMap(client.getDelegate());
    this.resultMappingList = new ArrayList();
    errorContext.setActivity("building a result map");
    errorContext.setObjectId(id + " result map");
    resultMap.setId(id);
    resultMap.setXmlName(xmlName);
    resultMap.setResource(errorContext.getResource());
    if (groupBy != null && groupBy.length() > 0) {
      StringTokenizer parser = new StringTokenizer(groupBy, ", ", false);
      while (parser.hasMoreTokens()) {
        resultMap.addGroupByProperty(parser.nextToken());
      }
    }
    resultMap.setResultClass(resultClass);
    errorContext.setMoreInfo("Check the extended result map.");
    if (extendsResultMap != null) {
      ResultMap extendedResultMap = (ResultMap) client.getDelegate().getResultMap(extendsResultMap);
      ResultMapping[] resultMappings = extendedResultMap.getResultMappings();
      for (int i = 0; i < resultMappings.length; i++) {
        resultMappingList.add(resultMappings[i]);
      }
      List nestedResultMappings = extendedResultMap.getNestedResultMappings();
      if (nestedResultMappings != null) {
        Iterator iter = nestedResultMappings.iterator();
        while (iter.hasNext()) {
          resultMap.addNestedResultMappings((ResultMapping) iter.next());
        }
      }
      if (groupBy == null || groupBy.length() == 0) {
        if (extendedResultMap.hasGroupBy()) {
          Iterator i = extendedResultMap.groupByProps();
          while (i.hasNext()) {
            resultMap.addGroupByProperty((String) i.next());
          }
        }
      }
    }
    errorContext.setMoreInfo("Check the result mappings.");
    resultMappingIndex = resultMappingList.size();
    resultMap.setResultMappingList(resultMappingList);
    client.getDelegate().addResultMap(resultMap);
  }

  /**
   * Sets the discriminator.
   *
   * @param columnName
   *          the column name
   * @param columnIndex
   *          the column index
   * @param javaClass
   *          the java class
   * @param jdbcType
   *          the jdbc type
   * @param nullValue
   *          the null value
   * @param typeHandlerImpl
   *          the type handler impl
   */
  public void setDiscriminator(String columnName, Integer columnIndex, Class javaClass, String jdbcType,
      String nullValue, Object typeHandlerImpl) {
    TypeHandler handler;
    if (typeHandlerImpl != null) {
      if (typeHandlerImpl instanceof TypeHandlerCallback) {
        handler = new CustomTypeHandler((TypeHandlerCallback) typeHandlerImpl);
      } else if (typeHandlerImpl instanceof TypeHandler) {
        handler = (TypeHandler) typeHandlerImpl;
      } else {
        throw new RuntimeException("The class '' is not a valid implementation of TypeHandler or TypeHandlerCallback");
      }
    } else {
      handler = config.resolveTypeHandler(client.getDelegate().getTypeHandlerFactory(), resultMap.getResultClass(), "",
          javaClass, jdbcType, true);
    }
    ResultMapping mapping = new ResultMapping();
    mapping.setColumnName(columnName);
    mapping.setJdbcTypeName(jdbcType);
    mapping.setTypeHandler(handler);
    mapping.setNullValue(nullValue);
    mapping.setJavaType(javaClass);
    if (columnIndex != null) {
      mapping.setColumnIndex(columnIndex.intValue());
    }
    discriminator = new Discriminator(delegate, mapping);
    resultMap.setDiscriminator(discriminator);
  }

  /**
   * Adds the discriminator sub map.
   *
   * @param value
   *          the value
   * @param resultMap
   *          the result map
   */
  public void addDiscriminatorSubMap(Object value, String resultMap) {
    if (discriminator == null) {
      throw new RuntimeException("The discriminator is null, but somehow a subMap was reached.  This is a bug.");
    }
    discriminator.addSubMap(value.toString(), resultMap);
  }

  /**
   * Adds the result mapping.
   *
   * @param propertyName
   *          the property name
   * @param columnName
   *          the column name
   * @param columnIndex
   *          the column index
   * @param javaClass
   *          the java class
   * @param jdbcType
   *          the jdbc type
   * @param nullValue
   *          the null value
   * @param notNullColumn
   *          the not null column
   * @param statementName
   *          the statement name
   * @param resultMapName
   *          the result map name
   * @param impl
   *          the impl
   */
  public void addResultMapping(String propertyName, String columnName, Integer columnIndex, Class javaClass,
      String jdbcType, String nullValue, String notNullColumn, String statementName, String resultMapName,
      Object impl) {
    errorContext.setObjectId(propertyName + " mapping of the " + resultMap.getId() + " result map");
    TypeHandler handler;
    if (impl != null) {
      if (impl instanceof TypeHandlerCallback) {
        handler = new CustomTypeHandler((TypeHandlerCallback) impl);
      } else if (impl instanceof TypeHandler) {
        handler = (TypeHandler) impl;
      } else {
        throw new RuntimeException(
            "The class '" + impl + "' is not a valid implementation of TypeHandler or TypeHandlerCallback");
      }
    } else {
      handler = config.resolveTypeHandler(client.getDelegate().getTypeHandlerFactory(), resultMap.getResultClass(),
          propertyName, javaClass, jdbcType, true);
    }
    ResultMapping mapping = new ResultMapping();
    mapping.setPropertyName(propertyName);
    mapping.setColumnName(columnName);
    mapping.setJdbcTypeName(jdbcType);
    mapping.setTypeHandler(handler);
    mapping.setNullValue(nullValue);
    mapping.setNotNullColumn(notNullColumn);
    mapping.setStatementName(statementName);
    mapping.setNestedResultMapName(resultMapName);
    if (resultMapName != null && resultMapName.length() > 0) {
      resultMap.addNestedResultMappings(mapping);
    }
    mapping.setJavaType(javaClass);
    if (columnIndex != null) {
      mapping.setColumnIndex(columnIndex.intValue());
    } else {
      resultMappingIndex++;
      mapping.setColumnIndex(resultMappingIndex);
    }
    resultMappingList.add(mapping);
    resultMap.setResultMappingList(resultMappingList);
  }

}
